/*
 Copyright (C) 2011 Salil Bhagurkar

 This file is part of illusion

 illusion is free software: you can redistribute it and/or modify
 it under the terms of the GNU Lesser General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 illusion is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU Lesser General Public License for more details.

 You should have received a copy of the GNU Lesser General Public License
 along with illusion.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <kernel/init.h>
#include <arch/init.h>
#include <klib/lib.h>
#include <klib/format.h>
#include <kernel/kmalloc.h>
#include <arch/asmdecl.h>
#include <klib/memory.h>
#include <klib/string.h>
#include <klib/bitmap.h>
#include <kernel/console.h>
#include <kernel/errors.h>
#include <kernel/page.h>
#include <kernel/vmpage.h>
#include <arch/power.h>
#include <arch/arch.h>
#include <arch/page.h>
#include <kernel/module.h>
#include <kernel/process.h>
#include <kernel/vfs.h>
#include <tests/tests.h>
#include <kernel/build.h>
#include <dev/dev.h>
#include <kernel/rdisk.h>
#include <kernel/sched.h>
#include <kernel/ilapp.h>
#include <kernel/fs.h>
#include <kernel/power.h>
#include <kernel/timing.h>
#include <kernel/sys.h>

/*
 * This variable is incremented when the system proceeds in initiation
 */

uint_t init_state = 0;

static void set_init_state(char new_init_state)
{
	init_state = new_init_state;
	perform_tests(new_init_state);
}



//The main kernel vmmap
struct vmmap kernel_vmmap;

static int phys_vm_base_init()
{
	initmsg("kernel_vmmap: 0x%x\n", &kernel_vmmap);
	memset(&kernel_vmmap, 0, sizeof(struct vmmap));

	memset(kernel_vmmap.vm_pagemap, 0, PAGEMAP_SZ);
	kernel_vmmap.vm_pagemap_start = null;
	return 0;
}

static void __map_range(void *start, void *end, uint_t flags)
{
	void *phys;
	for (phys = start; phys < end; phys += PAGE_SIZE) {
		vm_map_address(&kernel_vmmap, phys, phys, flags);
	}

}

static int map_init()
{
	init_page_sys(&kernel_vmmap.page_sys, 0, (void *) APIMOD_DEFAULT_LOAD);
	vm_reserve_page(&kernel_vmmap, kernel_vmmap.page_sys.mav);
	vm_reserve_page(&kernel_vmmap, kernel_vmmap.page_sys.mava);

	initmsg("Identity mapping kernel pages\n");
	//Map in everything used in physical pages at the same address
	//Start from PAGE_SIZE, to leave first page for detecting null point dereference

	__map_range((void *)PAGE_SIZE, (void *)_kernel_start, PAGE_PRESENT | PAGE_WRITE);
	__map_range((void *)_kernel_start, (void *)_kernel_data, PAGE_PRESENT); //Code read-only section
	__map_range((void *)_kernel_data, (void *)_kernel_end, PAGE_PRESENT | PAGE_WRITE);

	initmsg("Enabling page translation\n");

	vm_set_online(null, &kernel_vmmap);
	enable_page_translation();

	return 0;
}


struct vfs_node *ramdisk_root;
static int vfs_init()
{
	initmsg("Creating vfs devices\n");
	ramdisk_root = vfs_create_device("illusion", 1)->root;
	initmsg("Creating vfs nodes\n");
	vfs_create(ramdisk_root, "bootmodules");
	vfs_create(ramdisk_root, "rdisk");
	vfs_create(ramdisk_root, "console");
	vfs_create(ramdisk_root, "modules");
	vfs_process = 	vfs_create(ramdisk_root, "process");
	vfs_fs = 	vfs_create(ramdisk_root, "fs");
	return 0;
}



static int load_rdisk_module()
{
	initmsg("Loading rdisk..\n");
	struct vfs_node *rdisk_module, *rdisk_fs;
	int err = vfs_get(&rdisk_module, null, "illusion:/bootmodules/module0");
	if(err != 0) {
		initmsg("Could not find rdisk module\n");
		return err;
	}
	vfs_get(&rdisk_fs, null, "illusion:/rdisk");
	void *data = kmalloc(rdisk_module->length, "rdisk");
	vfs_read(rdisk_module, "data", data, 0, rdisk_module->length);

	struct rdisk_file_desc *desc = (struct rdisk_file_desc *)data;
	if(desc->length == 0) {
		console_printf("It is %u,%u,%u,%u,%u\n", sizeof(unsigned long long), sizeof(unsigned long), sizeof(unsigned int), sizeof(unsigned short), sizeof(unsigned char));
		initmsg("rdisk module descriptor length is zero.\n");
		return ESTINV;
	}

	while(desc->length != 0) {
		initmsg("Found file [%s] of size %u B @ %u\n", desc->file_name, desc->length, desc->offset);
		struct vfs_node *n = vfs_create(rdisk_fs, desc->file_name);
		vfs_write(n, "data", data + desc->offset, 0, desc->length);
		desc++;
	}
	//Delete the rdisk package
	vfs_delete(rdisk_module);
	vfs_sync(rdisk_module);
	return 0;
}

static void show_memory()
{
	uint_t alloc_pages = phys_total_count() - phys_count_free();
	initmsg("Physical Memory used: %u KiB\n", (alloc_pages * PAGE_SIZE) / 1024);
}


static void idle_process()
{
	while(1) {
		__asm__ volatile ("hlt");
	}
}

static void print_ident()
{
	struct vfs_node *ident_file;
	if(vfs_get(&ident_file, null, "illusion:/rdisk/ident.txt"))
		return;
	char *buf = (char *)kmalloc(ident_file->length, "ident");
	vfs_read(ident_file, "data", buf, 0, ident_file->length);
	console_write(buf, ident_file->length);
	kfree(buf, ident_file->length, "ident");
}

static void home_process()
{
	debug("Home process started. Starting idle, ishell..\n");

	//Add the idle process on low priority
	struct process *idle_proc;
	create_kernel_process(&idle_proc, "idle_process", idle_process);
	sched_change_prio(idle_proc, PRIO_LOW);
	sched_run_process(idle_proc);

	set_init_state(INIT_PROCESS);

	set_init_state(INIT_SYSTEM);

	show_memory();

	console_printf("Boot success!\n");
	console_printf("Illusion\nCopyright (C) 2011 Salil Bhagurkar\n");
	console_printf("Phase-4\n");
	console_printf("Build Date: %u\nBuild Number: %u\n", BUILD_DATE, BUILD_NUMBER);
	print_ident();

//	while(true) {
//		console_printf("Free pages = %u\n", phys_count_free());
//		struct process *umtest_proc;
//		struct vfs_node *umtest_fs;
//		vfs_get(&umtest_fs, null, "illusion:/rdisk/cat.exe");
//		char *args = "sys:/page/free";
//		uint len = strlen(args) + 1;
//		create_process(&umtest_proc, umtest_fs, args, len, 0);
//		sched_run_process(umtest_proc);
//		void *data;
//		event_wait(&data, umtest_proc->evt_exit);
//		process_kill(umtest_proc);
//		//ms_delay(1000);
//	}

	//Create the ishell process
	struct process *ishell_proc;
	struct vfs_node *ishell_fs;

	restart_ishell:
	if(vfs_get(&ishell_fs, null, "illusion:/rdisk/ishell.exe")) {
		assertv(false, "ishell.exe not found\n");
	}

	create_process(&ishell_proc, ishell_fs, null, 0, 0);
	sched_run_process(ishell_proc);

	//Wait for the ishell process to exit
	void *data;
	event_wait(&data, ishell_proc->evt_exit);

	console_printf("Oops! Ishell exited.. Restarting..\n");
	process_kill(ishell_proc);

	ms_delay(1000);

	goto restart_ishell;
}


static void start_user_init()
{
	//Start the home process which will start and maintain the user and
	//idle processes
	struct process *home_proc;

	create_kernel_process(&home_proc, "home_process", home_process);

	//Add to run list
	sched_run_process(home_proc);

	sched_release();

	assertv(false, "Home release failed!\n");
}


static int kernel_init2()
{
	int err;

	set_init_state(INIT_ZERO);

	err = setup_processor();
	if (err != 0)
		return err;
	set_init_state(INIT_PROCESSOR);

	err = console_init();
	//Set the init state to console, as if we have some error in the
	//console setup itself, we still want to try to display the error to
	//any available console driver
	set_init_state(INIT_CONSOLE);
	if (err != 0)
		return err;

	console_printf("Booting Illusion...\n");

	debug("_kernel_start = 0x%x\n", _kernel_start);
	debug("_kernel_data  = 0x%x\n", _kernel_data);
	debug("_kernel_bss   = 0x%x\n", _kernel_bss);
	debug("_kernel_end   = 0x%x\n", _kernel_end);

	//Perform the less urgent initialization of the architecture
	//after starting up the console
	err = arch_init2();
	if(err != 0)
		return err;
	set_init_state(INIT_PROCESSOR2);

	//Setup the kernel vmmap, the bitmap within it and
	//the physical page bitmap
	err = phys_vm_base_init();
	if (err != 0)
		return err;
	set_init_state(INIT_BITMAPS);

	//This will reserve any memory used by modules
	//We ignore error thrown by this, when there are no modules available
	err = reserve_module_memory();
	if(err != 0)
		return err;
	set_init_state(INIT_RESERVE_MODULE_MEM);

	//This is called to reserve any memory used by the kernel
	err = reserve_memory();
	if (err != 0)
		return err;
	set_init_state(INIT_RESERVE_MEM);

	//Now map the reserved memory in identity mapping mode
	err = map_init();
	if (err != 0)
		return err;
	set_init_state(INIT_MAP);

	//Create the vfs initial ramdisk device and add shit to it
	err = vfs_init();
	if (err != 0)
		return err;
	set_init_state(INIT_VFS);

	//Register modules, for which memory was already reserved.
	//The module sub system will then load them and initialize them
	err = register_modules();
	if(err != 0)
		return err;
	set_init_state(INIT_REG_MODS);

	//Load the rdisk module and separate all the files into the VFS ramdisk
	err = load_rdisk_module();
	if(err != 0)
		return err;
	set_init_state(INIT_RDISK);

	err = timing_init();
	if(err != 0)
		return err;
	set_init_state(INIT_TIMING);

	//Start up the kernel device drivers
	err = kernel_devs_init();
	if (err != 0)
		return err;
	set_init_state(INIT_KERNEL_DEVS);

	//Console VFS init
	err = console_vfs_init();
	if(err != 0)
		return err;
	set_init_state(INIT_CONSOLE_VFS);

	//Start up the power interface
	err = power_init();
	if(err != 0)
		return err;
	set_init_state(INIT_POWER);

	//Load the API module to allow user mode applications to
	//call the system API
	err = load_apimod("illusion:/rdisk/apimod.exe", PAGE_PRESENT | PAGE_USER);
	if(err != 0)
		return err;
	set_init_state(INIT_APIMOD);

	//Start the kernel system interface to the outside world
	err = sys_vfs_init();
	if(err != 0)
		return err;
	set_init_state(INIT_SYS_VFS);

	//Start the system interface for kmalloc
	err = page_sys_init();
	if(err != 0)
		return err;
	set_init_state(INIT_PAGE_SYS);

	//Start the system interface for console
	err = console_sys_init();
	if(err != 0)
		return err;
	set_init_state(INIT_CONSOLE_SYS);

	start_user_init();

	return 0;
}

void kernel_init()
{
	int err;
		
	err = kernel_init2();

	if (err) {
		if (init_state >= INIT_CONSOLE)
			console_printf("Init failed with error [%s] @ state %u\n", get_error_name(err), init_state);
		init_state = INIT_FAILED;
	}
	halt_system();
}
